import base64
import math
import operator
import os
import time
from functools import reduce

from PIL.Image import Image
from selenium import webdriver
from Operate.Logger import Logger
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver import DesiredCapabilities, ActionChains

from Util.SiWeiUtils import SiWeiUtils


class BaseOperate(object):
    """
       基础页面操作类
    """

    # 初始化使用对应的浏览器
    def __init__(self, browser='chrome'):
        browser_type = browser
        if browser_type == 'ie':
            self.driver = webdriver.Ie()
            self.window_size = self.driver.get_window_size()
        elif browser_type == 'chrome':
            self.driver = webdriver.Chrome()
            self.window_size = self.driver.get_window_size()
        elif browser_type == 'firefox':
            self.driver = webdriver.Firefox()
            self.window_size = self.driver.get_window_size()

    # def __init__(self, browser='chrome', isMultitask=False):
    #     if isMultitask:
    #         self.initMultitaskDriver(browser, serverUrl='http://localhost:4444/wd/hub')
    #     else:
    #         self.initDriver(browser)
    #
    # def initDriver(self, browser='firefox'):
    #     """
    #     Run class initialization method, the default is proper
    #     to drive the Firefox browser,. Of course, you can also
    #     pass parameter for other browser, such as Chrome browser for the "Chrome",
    #     the Internet Explorer browser for "internet explorer" or "ie".
    #     """
    #     self.logger = Logger('BlackMamba.log', level='debug').logger
    #     if browser == "firefox":
    #         driver = webdriver.Firefox()
    #     elif browser == "chrome":
    #         driver = webdriver.Chrome()
    #     elif browser == "ie":
    #         driver = webdriver.Ie()
    #     try:
    #         self.driver = driver
    #         self.driver.implicitly_wait(5)
    #     except Exception:
    #         raise NameError("Not found this browser,You can enter 'firefox', 'chrome', 'ie'.")
    #
    # def initMultitaskDriver(self, browser='firefox', serverUrl='http://localhost:4444/wd/hub'):
    #     """
    #     Run class initialization method for multitask, the default is proper
    #     to drive the Firefox browser,. Of course, you can also
    #     pass parameter for other browser, such as Chrome browser for the "Chrome",
    #     the Internet Explorer browser for "internet explorer" or "ie".
    #     """
    #     self.logger = Logger('BlackMamba.log', level='debug').logger
    #     if browser == "firefox":
    #         driver = webdriver.Remote(command_executor=serverUrl,
    #                                   desired_capabilities=DesiredCapabilities.FIREFOX)
    #     elif browser == "chrome":
    #         driver = webdriver.Remote(command_executor=serverUrl,
    #                                   desired_capabilities=DesiredCapabilities.CHROME)
    #     elif browser == "ie":
    #         driver = webdriver.Remote(command_executor=serverUrl,
    #                                   desired_capabilities=DesiredCapabilities.INTERNETEXPLORER)
    #     try:
    #         self.driver = driver
    #         self.driver.implicitly_wait(5)
    #     except Exception:
    #         raise NameError("Not found this browser,You can enter 'firefox', 'chrome', 'ie'.")

    def get(self, url):
        """
        打开一个网页链接
        Open url,same as get.

        示例
        Usage:
        driver.get("https://www.baidu.com")
        """
        self.driver.get(url)

    def max_window(self):
        """
        最大化当前窗口
        Set browser window maximized.

        示例
        Usage:
        driver.max_window()
        """
        self.driver.maximize_window()

    def set_window_size(self, wide, high):
        """
        设置页面尺寸 传递宽和高
        Set browser window wide and high.

        Usage:
        driver.set_window_size(wide,high)
        """
        self.driver.set_window_size(wide, high)

    def wait(self, secsonds):
        """
        Implicitly wait.All elements on the page.
        等待时长

        :param secsonds : 等待时长
        Usage:
        driver.wait(10)
        """
        self.driver.implicitly_wait(secsonds)

    def find_element(self, element):
        """
        根据定位获取单个元素
        Judge element positioning way, and returns the element.

        :param element: 传递对应的’xpath=value‘
        示例
        Usage:
        driver.find_element("id=kw")
        """
        driver = self.driver
        if "=" not in element:
            raise NameError("SyntaxError: invalid syntax, lack of '='.")

        by = element[0: element.find("=")]
        value = element[element.find("=") + 1: len(element)]

        if by == "id":
            # return self.driver.find_element_by_id(value)
            return driver.find_element_by_id(value)
        elif by == "name":
            # return self.driver.find_element_by_name(value)
            return driver.find_element_by_name(value)
        elif by == "class":
            # return self.driver.find_element_by_class_name(value)
            return driver.find_element_by_class_name(value)
        elif by == "text":
            # return self.driver.find_element_by_link_text(value)
            return driver.find_element_by_link_text(value)
        elif by == "text_part":
            # return self.driver.find_element_by_partial_link_text(value)
            return driver.find_element_by_partial_link_text(value)
        elif by == "xpath":
            # return self.driver.find_element_by_xpath(value)
            return driver.find_element_by_xpath(value)
        elif by == "css":
            # return self.driver.find_element_by_css_selector(value)
            return driver.find_element_by_css_selector(value)
        else:
            raise NameError("Please enter the correct targeting elements,'id','name','class','text','xpath','css'.")

    def find_elements(self, element, index):
        """
        根据定位获取一组元素
        Judge element positioning way and index, and returns the element.

        :param element:  传递对应的’xpath=value‘
        Usage:
        driver.find_element("id=kw",1)
        """
        if "=" not in element:
            raise NameError("SyntaxError: invalid syntax, lack of '='.")

        by = element[0: element.find("=")]
        value = element[element.find("=") + 1:len(element)]

        if by == "id":
            return self.driver.find_elements_by_id(value)[index]
        elif by == "name":
            return self.driver.find_elements_by_name(value)[index]
        elif by == "class":
            return self.driver.find_elements_by_class_name(value)[index]
        elif by == "text":
            return self.driver.find_elements_by_link_text(value)[index]
        elif by == "text_part":
            return self.driver.find_elements_by_partial_link_text(value)[index]
        elif by == "xpath":
            return self.driver.find_elements_by_xpath(value)[index]
        elif by == "css":
            return self.driver.find_elements_by_css_selector(value)[index]
        elif by == "tagName":
            return self.driver.find_elements_by_tag_name(value)[index]
        else:
            raise NameError("Please enter the correct targeting elements,'id','name','class','text','xpath','css'.")

    def wait_element(self, element, seconds=30):
        """
        等待一个元素加载
        Waiting for an element to display.

        :param element:  传递对应的’xpath=value‘
        :param seconds:  等待时长
        Usage:
        driver.wait_element("id=kw",30)
        """
        if "=" not in element:
            raise NameError("SyntaxError: invalid syntax, lack of '='.")
        else:
            try:
                by = element[0:element.find("=")]
                value = element[element.find("=") + 1:len(element)]
                if by == "id":
                    WebDriverWait(self.driver, seconds, 1).until(
                        EC.visibility_of_element_located((By.ID, value)))
                elif by == "name":
                    WebDriverWait(self.driver, seconds, 1).until(
                        EC.visibility_of_element_located((By.NAME, value)))
                elif by == "class":
                    WebDriverWait(self.driver, seconds, 1).until(
                        EC.visibility_of_element_located((By.CLASS_NAME, value)))
                elif by == "text":
                    WebDriverWait(self.driver, seconds, 1).until(
                        EC.visibility_of_element_located((By.LINK_TEXT, value)))
                elif by == "xpath":
                    WebDriverWait(self.driver, seconds, 1).until(
                        EC.visibility_of_element_located((By.XPATH, value)))
                elif by == "css":
                    WebDriverWait(self.driver, seconds, 1).until(
                        EC.visibility_of_element_located((By.CSS_SELECTOR, value)))
                elif by == "tagName":
                    WebDriverWait(self.driver, seconds, 1).until(
                        EC.visibility_of_element_located((By.TAG_NAME, value)))
                else:
                    self.logger.error(
                        "Please enter the correct targeting elements,'id','name','class','text','xpath','css'.")
                    raise NameError(
                        "Please enter the correct targeting elements,'id','name','class','text','xpath','css'.")
            except Exception:
                self.logger.exception("Time out can not find the element,the screen shot is:" + self.get_screenshot())
                assert False

    def send_keys(self, element, text):
        """
        清除并发送输入框中的值
        Operation input content after clear.

        :param element:  传递对应的’xpath=value‘
        :param text:  传递的文本
        Usage:
        driver.send_keys("id=kw","selenium")
        """
        self.wait_element(element)
        self.find_element(element).clear()
        self.find_element(element).send_keys(text)

    def send_keyBoardsEvent(self, element, keyEvent):
        """
        发送键盘上的一个键
        Operation send key board event on target.

        :param element:  传递对应的’xpath=value‘
        :param keyEvent:  传递对应的’键位‘
        Usage:
        driver.send_keyBoardsEvent("id=kw","Keys.ENTER")
        """
        self.wait_element(element)
        self.find_element(element).send_keys(keyEvent)

    def send_keys_index(self, element, index, text):
        """
        发送文本,并且定义发送次数
        Operation input content after clear.

        :param element:  传递对应的’xpath=value‘
        :param index:  传递对应的’次数‘
        :param text:  传递对应的’文本‘
        Usage:
        driver.send_keys("id=kw",5,"selenium")
        """
        self.wait_element(element)
        self.find_elements(element, index).clear()
        self.find_elements(element, index).send_keys(text)

    def click(self, element):
        """
        点击页面元素
        It can click any text / image can be clicked
        Connection, check box, radio buttons, and even drop-down box etc..


        :param element: id=value
        Usage:
        driver.click("id=kw")
        """
        self.wait_element(element)
        self.find_element(element).click()

    def click_index(self, element, index):
        """
        点击自定义次数的元素
        It can click any text / image can be clicked
        Connection, check box, radio buttons, and even drop-down box etc..

        :param element: id=value
        :param index: 次数
        Usage:
        driver.click("id=kw",5)
        """
        self.wait_element(element)
        self.find_elements(element, index).click()

    def right_click(self, element):
        """
        右键点击
        Right click element.

        :param element: id=value
        Usage:
        driver.right_click("class=right")
        """
        self.wait_element(element)
        ActionChains(self.driver).context_click(self.find_element(element)).perform()

    def move_to_element(self, element):
        """
        移动到一个元素上悬停(鼠标悬停)
        Mouse over the element.

        :param element: id=value
        Usage:
        driver.move_to_element("css=choose")
        """
        ActionChains(self.driver).move_to_element(self.find_element(element)).perform()

    def double_click(self, element):
        """
        双击元素
        Double click element.

        :param element: id=value
        Usage:
        driver.double_click("name=baidu")
        """
        self.wait_element(element)
        ActionChains(self.driver).double_click(self.find_element(element)).perform()

    def drag_and_drop(self, source_element, target_element):
        """
        拖拽元素至一定的位置放置
        Drags an element a certain distance and then drops it.

        :param source_element: 初始元素位置
        :param target_element: 结束元素位置
        Usage:
        driver.drag_and_drop("id=s","id=t")
        """
        self.wait_element(source_element)
        self.wait_element(target_element)
        ActionChains(self.driver).drag_and_drop(self.find_element(source_element),
                                                self.find_element(target_element)).perform()

    def back(self):
        """
        返回键
        Back to old window.

        Usage:
        driver.back()
        """
        self.driver.back()

    def forward(self):
        """
        回退键
        Forward to old window.

        Usage:
        driver.forward()
        """
        self.driver.forward()

    def get_attribute(self, element, attribute):
        """
        获取元素属性
        Gets the value of an element attribute.

        :param element: id=kw
        :param attribute: 获取元素属性值
        Usage:
        driver.get_attribute("id=kw","attribute")
        """
        self.wait_element(element)
        return self.find_element(element).get_attribute(attribute)

    def get_text(self, element):
        """
        获得元素属性值文本
        Get element text information.

        :param element: 元素
        Usage:
        driver.get_text("name=johnny")
        """
        self.wait_element(element)
        return self.find_element(element).text

    def get_display(self, element):
        """
        判断元素是否存在,返回布尔型
        Gets the element to display,The return result is true or false.

        Usage:
        driver.get_display("id=ppp")
        """
        self.wait_element(element)
        return self.find_element(element).is_displayed()

    def get_title(self):
        """
        获得当前页面标题
        Get window title.

        Usage:
        driver.get_title()
        """
        return self.driver.title

    def get_url(self):
        """
        获得当前页面的url
        Get the URL address of the current page.

        Usage:
        driver.get_url()
        """
        return self.driver.current_url

    def get_screenshot_as_base64(self):
        """
        以base64的格式获取当前屏幕快照
        """
        return base64.b64decode(self.driver.get_screenshot_as_base64().encode('ascii'))

    def get_screenshot(self):
        """
        获得当前页面的截图
        Get the current window screenshot.

        Usage:
        driver.get_screenshot()
        """
        current_time = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
        current_date = time.strftime("%Y-%m-%d", time.localtime(time.time()))
        pic_path = os.path.abspath(os.path.dirname(os.getcwd())) + "\\Screenshot\\" + current_date
        pic_name = current_time + '.png'
        if os.path.exists(pic_path):
            pass
        else:
            # 创建多层级的文件夹
            os.makedirs(pic_path)
        self.driver.get_screenshot_as_file(pic_path + '\\' + pic_name)
        return pic_path + '\\' + pic_name

    def submit(self, element):
        """
        提交表格元素
        Submit the specified form.

        Usage:
        driver.submit("id=mainFrame")
        """
        self.wait_element(element)
        self.find_element(element).submit()

    def switch_to_frame(self, element):
        """
        移动到内嵌窗口中
        Switch to the specified frame.

        Usage:
        driver.switch_to_frame("id=mainFrame")
        """
        self.wait_element(element)
        self.driver.switch_to.frame(self.find_element(element))

    def switch_to_frame_out(self):
        """
        从内嵌窗口中离开
        Returns the current form machine form at the next higher level.
        Corresponding relationship with switch_to_frame () method.

        Usage:
        driver.switch_to_frame_out()
        """
        self.driver.switch_to.parent_frame()

    def switch_to_default(self):
        """
        移动到默认的窗口中
        Returns the current form machine form at the next higher level.
        Corresponding relationship with switch_to_frame () method.

        Usage:
        driver.switch_to_default()
        """
        self.driver.switch_to.default_content()

    def open_new_window(self):
        """
        打开一个新的窗口
        Open the new window and switch the handle to the newly opened window.

        Usage:
        driver.open_new_window()
        """
        current_windows = self.driver.current_window_handle
        all_handles = self.driver.window_handles
        for handle in all_handles:
            if handle != current_windows:
                self.driver.switch_to.window(handle)

    def F5(self):
        """
        刷新键
        Refresh the current page.

        Usage:
        driver.F5()
        """
        self.driver.refresh()

    def js(self, script):
        """
        js代码运行
        Execute JavaScript scripts.

        Usage:
        driver.js("window.scrollTo(200,1000);")
        """
        self.driver.execute_script(script)

    def accept_alert(self):
        """
        接受警告弹窗
        Accept warning box.

        Usage:
        driver.accept_alert()
        """
        self.driver.switch_to.alert.accept()

    def dismiss_alert(self):
        """
        取消警告弹窗
        Dismisses the alert available.

        Usage:
        driver.dismiss_alert()
        """
        self.driver.switch_to.alert.dismiss()

    def close(self):
        """
        关闭当前窗口
        Close the windows.

        Usage:
        driver.close()
        """
        self.driver.close()

    def quit(self):
        """
        退出浏览器
        Quit the driver and close all the windows.

        Usage:
        driver.quit()
        """
        self.driver.delete_all_cookies()
        self.driver.quit()

    def is_page_has_text(self, text):
        """
        判断当前页面是否存在指定的文字


        :param text:字符串类型，要判断是否存在的文字
        :return:布尔值，True代表存在，False代表不存在
        """
        nowtime = time.time()
        while self.driver.page_source.find(text) < 0:
            time.sleep(2)
            if time.time() - nowtime >= 30000:
                return False
            return True

    def is_toast_exist(self, text, timeout=30, poll_frequency=0.5):
        """
        判断toast是否存在

        :param text: toast的文案
        :param timeout: 超时时长, 默认30s
        :param poll_frequency: 查询频率
        :return:
        """
        try:
            toast_loc = ("xpath", ".//*[contains(@text,'%s')]" % text)
            WebDriverWait(self.driver, timeout, poll_frequency).until(EC.presence_of_element_located(toast_loc))
            return True
        except:
            return False

    def screenshot_part(self, element, img_name, bounds=None):
        """
            功能: 截图指定元素
            参数:
                :param element: 要获取的元素
                :param img_name: 要保存的名字
                :param bounds: 元素边界(对于无法定位到的元素, 可直接使用边界定位)
        """
        time.sleep(2)

        # 截取屏幕, 并打开该图像
        SiWeiUtils.get_screencap()
        img = Image.open('Screenshot/screen.png')

        if bounds:
            (x1, y1, x2, y2) = bounds
        else:
            # 获取待截取的元素的边界
            x1 = int(SiWeiUtils.convert_bounds_to_location(self.get_attribute(element, 'bounds'))[0])
            y1 = int(SiWeiUtils.convert_bounds_to_location(self.get_attribute(element, 'bounds'))[1])
            x2 = int(SiWeiUtils.convert_bounds_to_location(self.get_attribute(element, 'bounds'))[2])
            y2 = int(SiWeiUtils.convert_bounds_to_location(self.get_attribute(element, 'bounds'))[3])

        # 设置图像裁剪区域 (x左上，y左上，x右下,y右下)
        box1 = (x1, y1, x2, y2)

        # 图像裁剪
        image1 = img.crop(box1)

        # 存储裁剪得到的图像
        image1.save("Image/" + img_name + ".png")

    @staticmethod
    def image_contrast(img1="img1.png", img2="img2.png"):
        """
            功能: 比较两张图片的差异
            返回: 0.0表示是完全一致的图片, 数值越大, 图片的差异越大
        """
        image1 = Image.open("Image/" + img1)
        image2 = Image.open("Image/" + img2)

        h1 = image1.histogram()
        h2 = image2.histogram()

        result = math.sqrt(reduce(operator.add, list(map(lambda a, b: (a - b) ** 2, h1, h2))) / len(h1))

        return result

    def swipe_o(self, ele=None, location=None, direction='right', times=1):
        """
        根据用户输入的方向和次数, 滑动屏幕
        :param ele: 需要滑动区域的元素
        :param location: 需滑动的位置, 元组形式, eg. (x1, x2, y1, y2)
        :param direction: 滑动方向, 可选参数['up', 'down', 'left', 'right']
        :param times: 滑动次数
        :return: None
        """

        # 根据传入的元素解析滑动区域
        if ele:
            x1, y1, x2, y2 = SiWeiUtils.convert_bounds_to_location(self.get_ele_bounds(ele))
        # 根据传入的坐标值解析滑动区域
        elif location:
            x1, x2, y1, y2 = location
        # 根据屏幕解析滑动区域
        else:
            width = self.driver.window_size['width']
            length = self.driver.window_size['height']
            x1 = width * 0.25
            x2 = width * 0.75
            y1 = length * 0.25
            y2 = length * 0.75

        # 循环滑动
        for i in range(times):
            if direction == 'up':
                self.driver.swipe(x1, y2, x1, y1)
            if direction == 'down':
                self.driver.swipe(x1, y1, x1, y2)
            if direction == 'right':
                self.driver.swipe(x1, y1, x2, y1)
            if direction == 'left':
                self.driver.swipe(x2, y1, x1, y1)
        time.sleep(2)

    def get_ele_bounds(self, ele):
        """
        获取元素的边界

        :param ele: id=path
        :return: str, 形式如: [x1,y1][x2,y2]
        """
        return self.get_attribute(ele, 'bounds')

    def get_inner_ele_text(self, ele):
        """
        获取元素内部的文本值

        :param ele: 元素对象
        :return: str
        """
        return self.get_attribute(ele, 'innerText')

    def is_display(self, loc):
        """
        判断元素是否存在, 存在返回True, 不存在返回False

        :param loc: 元素的表达形式, 以元祖形式传递, eg. (By.ID,ID属性值)
        :return: bool
        """

        try:
            self.find_element(loc)
            return True
        except Exception as e:
            return False


if __name__ == '__main__':
    driver = BaseOperate(browser="chrome")
    driver.get("http://www.baidu.com")
    driver.max_window()  # 浏览器最大化
    # text = driver.get_display('xpath=//*[@id="app"]/div/div/div/p')
    # text = driver.is_display('xpath=//*[@id="app"]/div/div/div/p')
    # print(text)
    driver.get_screenshot()
    driver.close()
    # driver.send_keys("id=kw", "selenium")  # 该元素位置输入内容
    # time.sleep(2)
    # driver.click("id=su")
    # time.sleep(2)
    # driver.click("id=result_logo")  # 点击元素
    # time.sleep(2)
    # driver.F5()  # 刷新页面
    # driver.get_screenshot()  # 截图
    # time.sleep(2)
    # driver.back()  # 后退
    # time.sleep(2)
    # driver.forward()  # 前进
    # driver.close()
